package com.baitiaojun.video.support;

import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSONObject;
import com.baitiaojun.api.exception.ConditionException;
import com.baitiaojun.api.util.AESUtil;
import com.baitiaojun.api.util.ConstantUtil;
import com.baitiaojun.api.util.FileUtil;
import com.baitiaojun.api.util.HttpUtil;
import com.baitiaojun.api.vo.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import io.netty.util.internal.StringUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.ConnectException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @类名 FastDFSSupport
 * @作者 白条君
 * @创建日期 2022/12/14 10:32
 * @版本 1.0
 */
@Component
public class FastDFSSupport {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Resource
    private RedisSupport redisSupport;

    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    @Value("${fdfs.host}")
    private String host;

    @Value("${fdfs.port}")
    private String port;

    public void delete(String filePath) {
        fastFileStorageClient.deleteFile(filePath);
    }

    public FileInfo uploadAppendFile(MultipartFile file, String group, String md5, Integer sliceNo, Integer totalSize) throws Exception {
        String uploadSizeKey = ConstantUtil.UPLOAD_SIZE_KEY + md5;
        String uploadNoKey = ConstantUtil.UPLOAD_NO_KEY + md5;
        String pathKey = ConstantUtil.PATH_KEY + md5;
        //redis中获取偏移量或长度
        Object uploadSize = redisSupport.getValue(uploadSizeKey);
        long size;
        if (ObjectUtils.isNotEmpty(uploadSize)) {
            size = Long.parseLong(JSONObject.toJSONString(uploadSize));
        } else {
            size = file.getSize();
        }
        if (sliceNo.equals(1)) {
            //第一次分片上传，并把获取的地址和第一个分片编号添加到redis
            StorePath storePath = appendFileStorageClient.uploadAppenderFile(group, file.getInputStream(), size, FileUtil.getType(file));
            redisSupport.setValue(pathKey, storePath.getFullPath(), 1, TimeUnit.DAYS);
            redisSupport.setValue(uploadNoKey, sliceNo, 1, TimeUnit.DAYS);
        } else {
            //后续上传分片，获取第一次上传分片后返回的地址作为追加地址，然后对redis中分片编号+1，把原来的偏移量+当前分片的长度得到新的偏移量添加到redis
            String filePath = JSONObject.toJSONString(redisSupport.getValue(pathKey));
            filePath = filePath.substring(1, filePath.length() - 1);
            Map<String, String> splitFilePath = FileUtil.splitFilePath(filePath);
            String storageGroup = "group1";
            String storagePath = splitFilePath.get("filePath");
            long fileSize = file.getSize();
            appendFileStorageClient.modifyFile(storageGroup, storagePath, file.getInputStream(), fileSize, size);
            redisSupport.increment(uploadNoKey);
            size += fileSize;
        }
        redisSupport.setValue(uploadSizeKey, size, 1, TimeUnit.DAYS);
        Object uploadNo = redisSupport.getValue(uploadNoKey);
        String path;
        //设置当前进度
        FileInfo fileInfo = new FileInfo();
        fileInfo.setProcess(this.getPercent(sliceNo, totalSize));
        //清除临时文件和缓存
        if (uploadNo.equals(totalSize)) {
            //如果当前分片编号等于总的分片大小
            //清除临时文件夹中的分片文件
//            boolean isDelete = FileUtil.deleteFileByMd5(md5);
//            if (Boolean.FALSE.equals(isDelete)) {
//                throw new ConditionException("临时文件目录删除失败，目录名称为:" + md5);
//            }
            //从redis中获取文件地址赋给path
            path = JSONObject.toJSONString(redisSupport.getValue(pathKey));
            path = path.substring(1, path.length() - 1);
            //删除redis中path、no、size缓存
            List<String> keys = Arrays.asList(uploadNoKey, uploadSizeKey, pathKey);
            redisSupport.getRedisTemplate().delete(keys);
            //最后一个分片上传完毕后可以得到路径
            fileInfo.setPath(path);
            return fileInfo;
        } else {
            //如果当前分片编号不等于总的分片大小
            return fileInfo;
        }
    }

    public String getPercent(Integer sliceNo, Integer totalSize) {
        NumberFormat numberFormat = NumberFormat.getPercentInstance();
        double process = Double.valueOf(sliceNo) / totalSize;
        numberFormat.setMinimumFractionDigits(0);
        return numberFormat.format(process);
    }

    //分片交给前端处理，这里临时测试
    public void fileConvertSlice(MultipartFile file, String md5) throws IOException, ConditionException {
        int SLICE_SIZE = ConstantUtil.SLICE_SIZE;
        File tempFile = createTempFile(file);
        String type = FileUtil.getType(file);
        byte[] buffer;
        BufferedOutputStream bufferedOutputStream;
        RandomAccessFile randomAccessFile;
        String path;
        //根据每天的日期和md5动态创建临时存储分片的文件夹
        StringBuilder builder = new StringBuilder(ConstantUtil.TEMP_FILE_SLICE_PATH + "/" + LocalDate.now() + "/" + md5 + "/");
        Path directories = Files.createDirectories(Paths.get(builder.toString()));
        if (ObjectUtils.isEmpty(directories)) {
            throw new ConditionException("临时文件路径创建失败");
        }
        int index = builder.toString().lastIndexOf("/");
        int count = 0;
        //创建一个分片存储的本地临时目录
        for (long i = 0; i < file.getSize(); i += SLICE_SIZE) {
            //每次获得新的randomAccessFile类用来进行分片处理
            randomAccessFile = new RandomAccessFile(tempFile, "r");
            //每次偏移到指定的其实位置开始读取
            randomAccessFile.seek(i);
            //每次创建缓冲区用来限定读取的分片大小和临时保存分片数据
            buffer = new byte[SLICE_SIZE];
            //每次读取临时文件的一部分数据到缓冲区中，同时获取当前分片的大小
            int sliceSize = randomAccessFile.read(buffer);
            path = builder.append(count + 1).append(".").append(type).toString();
            //将分片写入到本地临时目录保存
            bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(path));
            //根据缓存区数据和真实的文件大小来将数据写入到指定的路径下
            bufferedOutputStream.write(buffer, 0, sliceSize);
            //人为冲刷输出流所操作的的缓冲区，所有在buffer缓冲区中的数据被冲刷出去
            bufferedOutputStream.flush();
            //关闭流的同时就会冲刷所有buffer缓冲区中的数据，将其送出
            bufferedOutputStream.close();
            randomAccessFile.close();
            //清除倒数第一个"/"之后的内容
            builder.delete(index + 1, builder.length());
            //统计分片数量返回给前端
            count++;
        }
        //清除临时文件
        tempFile.delete();
        //返回分片
    }

    //临时测试
    private File createTempFile(MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename();
        if (ObjectUtils.isEmpty(originalFilename)) {
            throw new ConnectException("获取文件名称失败");
        }
        String[] fileName = originalFilename.split("\\.");
        File tempFile = File.createTempFile("tempFile", fileName[1]);
        //将file转移成一个tempfile
        file.transferTo(tempFile);
        return tempFile;
    }

    public void getPartialContent(HttpServletRequest request, HttpServletResponse response, String filePath) throws Exception {
        if (StringUtil.isNullOrEmpty(filePath)) {
            throw new ConditionException("视频路径不能为空");
        }
        filePath = filePath.replace(" ", "+");
        //对filePath进行解密
        AESUtil aesUtil = new AESUtil(ConstantUtil.AES_SEED);
        String decryptFilePath = aesUtil.decrypt(filePath);
        //获取group和path
        Map<String, String> filePathMap = FileUtil.splitFilePath(decryptFilePath);
        String group = filePathMap.get("group");
        String path = filePathMap.get("filePath");
        //定义访问文件服务器的url
        StringBuilder urlStr = new StringBuilder("http://");
        urlStr.append(host).append(":").append(port).append("/").append(group).append("/").append(path);
        String url = urlStr.toString();
        //获取请求头参数的名称列表进行遍历，然后把参数名称对应的值添加到一个map中
        Map<String,Object> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        String headerName;
        while (headerNames.hasMoreElements()) {
            headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        //通过获取FIleInfo得到总文件大小
        com.github.tobato.fastdfs.domain.fdfs.FileInfo fileInfo = fastFileStorageClient.queryFileInfo(group, path);
        long fileSize = fileInfo.getFileSize();
        //设置当请求的range为空,初始化一个range值（起始值为0，末尾值为总文件大小-1）
        long start = 0;
        long end = fileSize - 1;
        //获取header中用户所请求的的文件分片范围,Range: bytes=start-end
        String range = request.getHeader("Range");
        //如果请求的range不为空，对其进行split分组，拿到范围的值
        if (StringUtils.isNotBlank(range)) {
            //如果范围大小为2，说明没有末尾值，获取开始的值等于startOrEnd[1]
            String[] startOrEnd = range.split("bytes=|-");
            if (startOrEnd.length >= 2) {
                start = Long.parseLong(startOrEnd[1]);
            }
            //如果范围大小为3，说明有末尾值，进行了第1步骤操作以后，获取末尾的值等于startOrEnd[2]
            if (startOrEnd.length == 3) {
                end = Long.parseLong(startOrEnd[2]);
            }
        }
        //得到当前响应的分片长度：请求的末尾值-请求的起始值+1
        long len = end - start + 1;
        //定义响应头中当前响应的总大小，content-range值：bytes 响应起始值-响应末尾值/文件总大小
        StringBuilder contentRangeStr = new StringBuilder("bytes ");
        String contentRange = contentRangeStr.append(start).append("-").append(end).append("/")
                .append(fileSize).toString();
        //  1、content-range：响应的分片范围（bytes 起始值-末尾值/文件总大小）
        //	2、accept-range：bytes
        //	3、content-type：video/mp4
        //	4、content-length：响应的分片长度
        //	5、ststus：206 HTTP_PARTIAL（表示获取的分片文件）
        response.setHeader("Content-Range", contentRange);
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Type", "video/mp4");
        response.setContentLength(Integer.parseInt(String.valueOf(len)));
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        //使用httputil的get方法得到outputstream进行输出，参数为map、response、url
        HttpUtil.get(url, headers, response);
    }

    public String downLoadFile(String group, String filePath) {
        String tempPath = ConstantUtil.TEMP_FILE_DOWNLOAD_PATH + FileUtil.getFileName(filePath);
        File file = new File(tempPath);
        try (OutputStream outputStream = new FileOutputStream(file)) {
            byte[] fileObject = fastFileStorageClient.downloadFile(group, filePath, new DownloadByteArray());
            outputStream.write(fileObject);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return tempPath;
    }

    public void downLoadFile(String group, String filePath, String localFileUrl) {
        fastFileStorageClient.downloadFile(group, filePath, (DownloadCallback<Object>) ins -> {
            OutputStream ous = null;
            try {
                File file = new File(localFileUrl);
                ous = new FileOutputStream(file);
                int len;
                byte[] buffer = new byte[1024];
                while ((len = ins.read(buffer)) != -1) {
                    ous.write(buffer, 0, len);
                }
                ous.flush();
            } finally {
                if (ObjectUtils.isNotEmpty(ous)) {
                    try {
                        ous.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (ObjectUtils.isNotEmpty(ins)) {
                    try {
                        ins.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return "success";
        });
    }

    public String uploadCommonFile(File file, String group, String fileExtName) throws IOException {
        FileInputStream ins = new FileInputStream(file);
        StorePath storePath = fastFileStorageClient.uploadFile(group, ins, ins.available(), fileExtName);
        return storePath.getFullPath();
    }

    public String uploadCommonFile(InputStream ins, String group, String fileExtName) throws IOException {
        StorePath storePath = fastFileStorageClient.uploadFile(group, ins, ins.available(), fileExtName);
        return storePath.getFullPath();
    }

    public void getThumbnail(String group, HttpServletRequest request, HttpServletResponse response, String filePath) throws Exception {
        //定义访问文件服务器的url
        StringBuilder urlStr = new StringBuilder("http://");
        urlStr.append(host).append(":").append(port).append("/").append(group).append("/").append(filePath);
        String url = urlStr.toString();
        //获取请求头参数的名称列表进行遍历，然后把参数名称对应的值添加到一个map中
        Map<String,Object> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        String headerName;
        while (headerNames.hasMoreElements()) {
            headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        //通过获取FIleInfo得到总文件大小
        long fileSize = fastFileStorageClient.queryFileInfo(group, filePath).getFileSize();
        //	2、accept-range：bytes
        response.setHeader("Accept-Ranges", "bytes");
        response.setContentLength(Integer.parseInt(String.valueOf(fileSize)));
        //使用httputil的get方法得到outputstream进行输出，参数为map、response、url
        HttpUtil.get(url, headers, response);
    }
}